package com.ccrfid.rmc6.service.business.chongqing;

import com.ccrfid.rmc6.entity.business.chongqing.CQPatrolLocationEntity;
import com.ccrfid.rmc6.entity.business.chongqing.CQPatrolRecordEntity;
import com.ccrfid.rmc6.entity.business.chongqing.CQPatrolRoadEntity;
import com.ccrfid.rmc6.entity.personnel.PersonnelEntity;
import com.ccrfid.rmc6.entity.poi.PoiLocationEntity;
import com.ccrfid.rmc6.lbservice3.pojo.locate.PersonnelLocateInfo;
import com.ccrfid.rmc6.lbservice3.pojo.msg.MsgLocate;
import com.ccrfid.rmc6.pojo.Constants;
import com.ccrfid.rmc6.service.amq.MqMsgProducer;
import com.ccrfid.rmc6.service.business.chongqing.baodeng.BaodengDataService;
import com.ccrfid.rmc6.service.business.chongqing.baodeng.pojo.MsgPatrol;
import com.ccrfid.rmc6.service.business.chongqing.baodeng.pojo.PatrolScheduleDto;
import com.ccrfid.rmc6.service.personnel.PersonnelService;
import com.ccrfid.rmc6.service.poi.PoiLocationService;
import com.ccrfid.rmc6.util.DateUtil;
import com.ccrfid.rmc6.util.JsonUtil;
import com.ccrfid.rmc6.util.SleepUtil;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.stream.Collectors;

@Component
public class CQPatrolManager {
    private static final Logger logger = LoggerFactory.getLogger(CQPatrolManager.class);

    private static final String TOPIC = "open_patrol_event";
    private static final String TOPIC_BD = "open_bd_patrol_event";
    private static final int HEAD = 0x01;
    private static final int TAIL = 0x02;
    private static final int MIDDLE = 0x04;
    private Map<Long, String> patrolTaskMap = new ConcurrentHashMap<>();
    private Map<Long, PatrolLocation> patrolLocationMap = new ConcurrentHashMap<>();
    private Map<Long, ScheduledFuture<?>> futureMap = new ConcurrentHashMap<>();
    private Map<Long, PersonnelEntity> personnelMap = new ConcurrentHashMap<>();

    @Autowired
    private EventBus eventBus;
    @Autowired
    private TaskScheduler taskScheduler;
    @Autowired
    private MqMsgProducer mqMsgProducer;
    @Autowired
    private CQPatrolService patrolService;
    @Autowired
    private PoiLocationService poiLocationService;
    @Autowired
    private BaodengDataService baodengDataService;
    @Autowired
    private PersonnelService personnelService;

    @Value("${chongqing.daytime.start}")
    private Integer daytimeStart;
    @Value("${chongqing.daytime.stop}")
    private Integer daytimeStop;
    @Value("${chongqing.patrol.day.timeout}")
    private Integer dayTimeout;
    @Value("${chongqing.patrol.night.timeout}")
    private Integer nightTimeout;

    @PostConstruct
    private void init() {

        eventBus.register(this);

        initPatrolTask();

        initPatrolLocationMap();

        initPatrolPersonnel();
    }

    @Scheduled(cron = "1 0 * * * ?")
    //Scheduled(cron = "秒 分 时 日 月 星期")
    //定时任务，每个整点执行一次
    public void initPatrolPersonnel() {
        logger.debug("initPatrolPersonnel ...");
        String today = LocalDate.now().toString();
        List<PatrolScheduleDto> list = baodengDataService.getPatrolSchedule(today);
        if (list != null && list.size() > 0) {
            personnelMap.clear();

            for (PatrolScheduleDto dto : list) {
                PersonnelEntity personnelEntity = personnelService.getPersonnelBySn(dto.getPoliceId());
                if (personnelEntity != null) {
                    personnelMap.put(personnelEntity.getId(), personnelEntity);
                }
            }
        }
        logger.debug("PatrolPersonnel: {}", JsonUtil.INSTANCE.writeValueAsString(personnelMap.values()));
    }

    @JmsListener(destination = "ws_cq_patrol_road_create", containerFactory = "topicListenerCF")
    public void onPatrolRoadCreate(String content) {
        SleepUtil.sleepSeconds(1);
        CQPatrolRoadEntity roadEntity = JsonUtil.INSTANCE.readValue(content, CQPatrolRoadEntity.class);

        startTimeoutTask(roadEntity.getId(), roadEntity.getRoadName());
    }

    @JmsListener(destination = "ws_cq_patrol_road_delete", containerFactory = "topicListenerCF")
    public void onPatrolRoadDelete(String content) {
        SleepUtil.sleepSeconds(1);
        Long[] idList = JsonUtil.INSTANCE.readValue(content, Long[].class);
        if (idList != null && idList.length > 0) {
            for (Long roadId : idList) {
                cancelTimeoutTask(roadId);
            }
        }
        initPatrolLocationMap();
    }

    @JmsListener(destination = "ws_cq_patrol_location_create", containerFactory = "topicListenerCF")
    public void onPatrolLocationCreate(String content) {
        SleepUtil.sleepSeconds(1);
        Long[] idList = JsonUtil.INSTANCE.readValue(content, Long[].class);
        initPatrolLocationMap();
    }

    @JmsListener(destination = "ws_cq_patrol_location_delete", containerFactory = "topicListenerCF")
    public void onPatrolLocationDelete(String content) {
        SleepUtil.sleepSeconds(1);
        Long[] idList = JsonUtil.INSTANCE.readValue(content, Long[].class);
        initPatrolLocationMap();
    }

    @Subscribe
    public void onPersonnelLocateInfo(PersonnelLocateInfo locateInfo) {
        if (locateInfo.getPersonnelId() != null) {
            MsgLocate msgLocate = locateInfo.getMsgLocate();
            if (msgLocate.getCurrentPos() != null) {
                //1.通知离开区域: 区域内原来有此人，现在无此人
                patrolLocationMap.values().parallelStream()
                        .filter(location -> location.containsPersonnel(locateInfo.getPersonnelId())
                                && (location.floorNotEquals(msgLocate.getCurrentFloorNumber())
                                || location.notContainsPos(msgLocate.getCurrentPos()))
                        )
                        .forEach(location -> {
                            location.removePersonnel(locateInfo.getPersonnelId());
                            //判断是否巡更结束并开始超时计时
                            if (personnelMap.isEmpty() || personnelMap.containsKey(locateInfo.getPersonnelId())) {
                                String task = patrolTaskMap.get(location.getRoadId());
                                if (task != null && ((location.getPosType() & TAIL) > 0)) {
                                    CQPatrolRecordEntity recordEntity = createCQPatrolRecord(location, locateInfo,
                                            Constants.CQ_PATROL_EVENT.FINISH, task);
                                    patrolService.saveCQPatrolRecord(recordEntity);
                                    //send to mq
                                    mqMsgProducer.send2Topic(TOPIC, JsonUtil.INSTANCE.writeValueAsString(recordEntity));
                                    //超时计时开始
                                    startTimeoutTask(location.roadId, location.roadName);
                                    //清除task
                                    patrolTaskMap.remove(location.getRoadId());
                                    //向宝登平台发送汇总消息
                                    postMsgPatrol(recordEntity);
                                }
                            }
                        });
                //2.通知进入区域: 区域内原来无此人，现在有此人
                patrolLocationMap.values().parallelStream()
                        .filter(location -> location.notContainsPersonnel(locateInfo.getPersonnelId())
                                && location.floorEquals(msgLocate.getCurrentFloorNumber())
                                && location.containsPos(msgLocate.getCurrentPos())
                        )
                        .forEach(location -> {
                            location.addPersonnel(locateInfo.getPersonnelId());
                            //判断进入巡更区域
                            if (personnelMap.isEmpty() || personnelMap.containsKey(locateInfo.getPersonnelId())) {
                                if ((location.getPosType() & HEAD) > 0) {
                                    //巡更开始
                                    String task = patrolTaskMap.get(location.getRoadId());
                                    if (task == null) {
                                        //创建巡更任务id
                                        task = UUID.randomUUID().toString();
                                        patrolTaskMap.put(location.getRoadId(), task);
                                    }
                                    CQPatrolRecordEntity recordEntity = createCQPatrolRecord(location, locateInfo,
                                            Constants.CQ_PATROL_EVENT.START, task);
                                    patrolService.saveCQPatrolRecord(recordEntity);
                                    //send to mq
                                    mqMsgProducer.send2Topic(TOPIC, JsonUtil.INSTANCE.writeValueAsString(recordEntity));
                                    //取消超时计时
                                    cancelTimeoutTask(location.getRoadId());
                                } else if ((location.getPosType() & MIDDLE) > 0 || (location.getPosType() & TAIL) > 0) {
                                    //进入某个巡更点
                                    String task = patrolTaskMap.get(location.getRoadId());
                                    if (task != null) {
                                        CQPatrolRecordEntity recordEntity = createCQPatrolRecord(location, locateInfo,
                                                Constants.CQ_PATROL_EVENT.ENTER, task);
                                        patrolService.saveCQPatrolRecord(recordEntity);
                                        //send to mq
                                        mqMsgProducer.send2Topic(TOPIC, JsonUtil.INSTANCE.writeValueAsString(recordEntity));
                                        //取消超时计时
                                        cancelTimeoutTask(location.getRoadId());
                                    }
                                }
                            }
                        });
            } else if (msgLocate.getCurrentPos() == null) {
                //1.通知离开区域：区域内原来有此人，现在此人位置为空
                patrolLocationMap.values().parallelStream()
                        .filter(location -> location.containsPersonnel(locateInfo.getPersonnelId()))
                        .forEach(location -> {
                            location.removePersonnel(locateInfo.getPersonnelId());
                            //判断是否巡更结束并开始超时计时
                            if (personnelMap.isEmpty() || personnelMap.containsKey(locateInfo.getPersonnelId())) {
                                String task = patrolTaskMap.get(location.getRoadId());
                                if (task != null && ((location.getPosType() & TAIL) > 0)) {
                                    CQPatrolRecordEntity recordEntity = createCQPatrolRecord(location, locateInfo,
                                            Constants.CQ_PATROL_EVENT.FINISH, task);
                                    patrolService.saveCQPatrolRecord(recordEntity);
                                    //send to mq
                                    mqMsgProducer.send2Topic(TOPIC, JsonUtil.INSTANCE.writeValueAsString(recordEntity));
                                    //超时计时开始
                                    startTimeoutTask(location.roadId, location.roadName);
                                    //清除task
                                    patrolTaskMap.remove(location.getRoadId());
                                    //向宝登平台发送汇总消息
                                    postMsgPatrol(recordEntity);
                                }
                            }
                        });
            }
        }
    }

    //向宝登平台发送汇总消息
    private void postMsgPatrol(CQPatrolRecordEntity recordEntity) {
        MsgPatrol msgPatrol = new MsgPatrol();
        msgPatrol.setPoliceName(recordEntity.getPersonnelName());
        msgPatrol.setRoadId(recordEntity.getPatrolRoadId());
        msgPatrol.setRoadName(recordEntity.getPatrolRoadName());
        msgPatrol.setTimeout(0);
        msgPatrol.setPointCount(0);
        msgPatrol.setStartTime(DateUtil.toDateTimeStr(recordEntity.getRecordTime()));
        msgPatrol.setStopTime(DateUtil.toDateTimeStr(recordEntity.getRecordTime()));

        List<CQPatrolLocationEntity> locationList = patrolService.getCQPatrolLocationList(recordEntity.getPatrolRoadId());
        int count = locationList != null ? locationList.size() : 0;
        msgPatrol.setPatrolCount(count);

        List<CQPatrolRecordEntity> recordList = patrolService.getAllPatrolRecordByTask(recordEntity.getTask());
        if (recordList != null) {
            Set<Long> locationSet = new HashSet<>();
            for (CQPatrolRecordEntity entity : recordList) {
                locationSet.add(entity.getLocationId());
                if (Constants.CQ_PATROL_EVENT.START.id.equals(entity.getEventType())) {
                    msgPatrol.setStartTime(DateUtil.toDateTimeStr(entity.getRecordTime()));
                }
            }
            msgPatrol.setPointCount(locationList.size());
        }
        //推送到MQ和宝登平台
        mqMsgProducer.send2Topic(TOPIC_BD, JsonUtil.INSTANCE.writeValueAsString(msgPatrol));
        baodengDataService.postMsgPatrol(msgPatrol);
    }

    private void startTimeoutTask(Long roadId, String roadName) {
        if (!futureMap.containsKey(roadId)) {
            long delay = 0;
            int nowHour = LocalDateTime.now().getHour();
            if (nowHour >= daytimeStart && nowHour <= daytimeStop) {
                delay = dayTimeout * 60 * 1000;
            } else {
                delay = nightTimeout * 60 * 1000;
            }
            ScheduledFuture<?> future = taskScheduler.scheduleAtFixedRate(
                    new PatrolTimeoutTask(roadId, roadName, this),
                    new Date(System.currentTimeMillis() + delay), delay);
            futureMap.put(roadId, future);
            logger.debug("startTimeoutTask: {}, {}", roadId, roadName);
        }
    }

    private void cancelTimeoutTask(Long roadId) {
        ScheduledFuture<?> future = futureMap.get(roadId);
        if (future != null) {
            future.cancel(false);
            futureMap.remove(roadId);
            logger.debug("cancelTimeoutTask: {}", roadId);
        }
    }

    public void patrolTimeout(Long roadId, String roadName) {
        CQPatrolRecordEntity recordEntity = new CQPatrolRecordEntity();
        recordEntity.setPatrolRoadId(roadId);
        recordEntity.setPatrolRoadName(roadName);
        recordEntity.setEventType(Constants.CQ_PATROL_EVENT.TIMEOUT.id);
        recordEntity.setEventTypeRemark(Constants.CQ_PATROL_EVENT.TIMEOUT.name);
        recordEntity.setRecordTime(new Timestamp(System.currentTimeMillis()));
        //save
        patrolService.saveCQPatrolRecord(recordEntity);
        //send to mq
        mqMsgProducer.send2Topic(TOPIC, JsonUtil.INSTANCE.writeValueAsString(recordEntity));
    }

    private CQPatrolRecordEntity createCQPatrolRecord(PatrolLocation patrolLocation,
                                                      PersonnelLocateInfo locateInfo,
                                                      Constants.CQ_PATROL_EVENT event, String task) {
        CQPatrolRecordEntity recordEntity = new CQPatrolRecordEntity();
        recordEntity.setPatrolRoadId(patrolLocation.getRoadId());
        recordEntity.setPatrolRoadName(patrolLocation.getRoadName());
        recordEntity.setPersonnelId(locateInfo.getPersonnelId());
        recordEntity.setPersonnelName(locateInfo.getPersonnelName());
        PersonnelEntity personnelEntity = personnelService.getPersonnelById(locateInfo.getPersonnelId());
        if (personnelEntity != null) {
            recordEntity.setPersonnelSn(personnelEntity.getPersonnelSn());
        }
        recordEntity.setLocationId(patrolLocation.getLocationId());
        recordEntity.setLocationName(patrolLocation.getLocationName());
        recordEntity.setLocationSn(patrolLocation.getLocationSn());
        recordEntity.setEventType(event.id);
        recordEntity.setEventTypeRemark(event.name);
        Long time = locateInfo.getMsgLocate().getTime();
        recordEntity.setRecordTime(time != null ? new Timestamp(time) : new Timestamp(System.currentTimeMillis()));
        recordEntity.setTask(task);
        return recordEntity;
    }

    private void initPatrolTask() {
        logger.debug("initPatrolTask ...");
        List<CQPatrolRoadEntity> roadList = patrolService.getAllCQPatrolRoadList();
        if (roadList != null && roadList.size() > 0) {
            for (CQPatrolRoadEntity roadEntity : roadList) {
                CQPatrolRecordEntity record = patrolService.getPatrolRecordByRoadIdMax(roadEntity.getId());
                if (record != null && record.getPatrolRoadId() != null && record.getTask() != null) {
                    patrolTaskMap.put(record.getPatrolRoadId(), record.getTask());
                    logger.info("initPatrolTask: {}, {}, {}",
                            record.getPatrolRoadId(), record.getPatrolRoadName(), record.getTask());
                }
            }
        }
    }

    private void initPatrolLocationMap() {
        logger.debug("initPatrolLocationMap ...");
        //clear
        patrolLocationMap.clear();
        //init
        List<CQPatrolRoadEntity> roadList = patrolService.getAllCQPatrolRoadList();
        if (roadList != null && roadList.size() > 0) {
            for (CQPatrolRoadEntity roadEntity : roadList) {
                //1.开启路线巡更超时任务
                startTimeoutTask(roadEntity.getId(), roadEntity.getRoadName());
                //2.初始化数据
                List<CQPatrolLocationEntity> locationList = patrolService.getCQPatrolLocationList(roadEntity.getId());
                logger.debug("locationList.size: {}", locationList.size());
                if (locationList != null && locationList.size() > 0) {
                    if (locationList.size() == 1) {
                        //头-尾
                        CQPatrolLocationEntity headTail = locationList.get(0);
                        initPatrolLocation(roadEntity, headTail, HEAD ^ TAIL);
                    } else if (locationList.size() >= 2) {
                        //头
                        CQPatrolLocationEntity head = locationList.get(0);
                        initPatrolLocation(roadEntity, head, HEAD);
                        //尾
                        CQPatrolLocationEntity tail = locationList.get(locationList.size() - 1);
                        initPatrolLocation(roadEntity, tail, TAIL);
                        //中间
                        for (int i = 1; i < locationList.size() - 1; i++) {
                            CQPatrolLocationEntity middle = locationList.get(i);
                            initPatrolLocation(roadEntity, middle, MIDDLE);
                        }
                    }
                }
            }
        }
        logger.debug("PatrolLocation: {}", JsonUtil.INSTANCE.writeValueAsString(patrolLocationMap.values()));
    }

    private void initPatrolLocation(CQPatrolRoadEntity roadEntity, CQPatrolLocationEntity locationEntity, int position) {
        PoiLocationEntity poiLocation = poiLocationService.getPoiLocationById(locationEntity.getLocationId());
        logger.debug("initPatrolLocation: {}, {}, {}, {}", roadEntity.getRoadName(), poiLocation.getId(),
                poiLocation.getLocationName(), position);
        if (poiLocation != null) {
            PatrolLocation patrolLocation = new PatrolLocation(locationEntity.getId(), poiLocation, roadEntity, position);
            patrolLocationMap.put(patrolLocation.locationId, patrolLocation);
        }
    }

    private class PatrolLocation {
        private Long id;
        private Long locationId;
        private String locationName;
        private String locationSn;
        private Polygon locationPolygon;
        private Integer floorNumber;
        private Integer posType;

        private Long roadId;
        private String roadName;

        private Set<Long> personnelIdSet;   //巡更人员

        public PatrolLocation(Long id, PoiLocationEntity poiLocation, CQPatrolRoadEntity road, int posType) {
            this.id = id;
            this.locationId = poiLocation.getId();
            this.locationName = poiLocation.getLocationName();
            this.locationSn = poiLocation.getLocationSn();
            this.locationPolygon = poiLocation.getLocationPolygon();
            this.floorNumber = poiLocation.getFloorNumber();
            this.posType = posType;
            this.roadId = road.getId();
            this.roadName = road.getRoadName();
            this.personnelIdSet = ConcurrentHashMap.newKeySet();
        }

        public void addPersonnel(Long personnelId) {
            personnelIdSet.add(personnelId);
        }

        public void removePersonnel(Long personnelId) {
            personnelIdSet.remove(personnelId);
        }

        public void clearPersonnel() {
            personnelIdSet.clear();
        }

        public boolean containsPersonnel(Long personnelId) {
            return personnelIdSet.contains(personnelId);
        }

        public boolean notContainsPersonnel(Long personnelId) {
            return !containsPersonnel(personnelId);
        }

        public boolean containsPos(Point pos) {
            return locationPolygon.contains(pos);
        }

        public boolean notContainsPos(Point pos) {
            return !containsPos(pos);
        }

        public boolean floorEquals(Integer floorNumber) {
            return this.floorNumber.equals(floorNumber);
        }

        public boolean floorNotEquals(Integer floorNumber) {
            return !floorEquals(floorNumber);
        }


        public Long getId() {
            return id;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public Long getLocationId() {
            return locationId;
        }

        public void setLocationId(Long locationId) {
            this.locationId = locationId;
        }

        public String getLocationName() {
            return locationName;
        }

        public String getLocationSn() {
            return locationSn;
        }

        public void setLocationSn(String locationSn) {
            this.locationSn = locationSn;
        }

        public void setLocationName(String locationName) {
            this.locationName = locationName;
        }

        public Polygon getLocationPolygon() {
            return locationPolygon;
        }

        public void setLocationPolygon(Polygon locationPolygon) {
            this.locationPolygon = locationPolygon;
        }

        public Integer getFloorNumber() {
            return floorNumber;
        }

        public void setFloorNumber(Integer floorNumber) {
            this.floorNumber = floorNumber;
        }

        public Integer getPosType() {
            return posType;
        }

        public void setPosType(Integer posType) {
            this.posType = posType;
        }

        public Long getRoadId() {
            return roadId;
        }

        public void setRoadId(Long roadId) {
            this.roadId = roadId;
        }

        public String getRoadName() {
            return roadName;
        }

        public void setRoadName(String roadName) {
            this.roadName = roadName;
        }

        public Set<Long> getPersonnelIdSet() {
            return personnelIdSet;
        }

        public void setPersonnelIdSet(Set<Long> personnelIdSet) {
            this.personnelIdSet = personnelIdSet;
        }
    }
}
